<?php

// +----------------------------------------------------------------------
// | Library for song0229liu
// +----------------------------------------------------------------------

namespace kbitAdmin\tools;

/**
 * 控制器节点管理器
 * Class Node
 * @package kbitAdmin\tools
 */
class Node
{

    /**
     * 控制器忽略函数
     * @var array
     */
    protected static $ignore = ['initialize', 'success', 'error', 'redirect', 'fetch', 'assign', 'callback'];

    /**
     * 获取当前访问节点
     * @return string
     */
    public static function current()
    {
        $request = request();
        $replace_str_arr = [
            '.html',
            '.htm',
        ];
        $result = strtolower($request->pathinfo());
        foreach ($replace_str_arr as $v) {
            $result = str_replace($v, '', $result);
        }

        $path_arr = explode('/', $result);
        if(count($path_arr) == 2) $result .= '/index'; // 自动加上 index 方法
        return $result;
    }

    /**
     * 获取方法节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     * @throws \ReflectionException
     */
    public static function getMethodTreeNode($dir, $nodes = [])
    {
        foreach (self::scanDir($dir) as $file) {
            if(strstr($file, '/api/')) continue;
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/controller/(.+)|', $filename, $matches)) continue;
            if (class_exists($classname = env('app_namespace') . str_replace('/', '\\', substr($matches[0], 0, -4)))) {
                foreach ((new \ReflectionClass($classname))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
                    list($function, $comment) = [$method->getName(), $method->getDocComment()];
                    if (stripos($function, '_') === 0 || in_array($function, self::$ignore)) continue;
                    $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                    if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;
                    $node = self::parseString("{$matches[1]}/{$controller}") . '/' . strtolower($function);
                    $nodes[$node] = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', preg_replace("/\s/", '', $comment));
                    if (stripos($nodes[$node], '@') !== false) $nodes[$node] = '';
                }
            }
        }
        return $nodes;
    }
    /**
     * 获取插件方法节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     * @throws \ReflectionException
     */
    public static function getAddonsMethodTreeNode($dir, $nodes = [], $folder = 'controller', $addons_name = '')
    {   
        foreach (self::scanDir($dir) as $file) {
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/'.$folder.'/(.+)|', $filename, $matches)) continue;
            $classname = env('app_namespace') . str_replace('/', '\\', substr($matches[0], 0, -4));
            if(strstr($file, 'addons')){
                $classname = '\\kbitAddons\\' . str_replace('addons-', '', $addons_name) . \str_replace('src\\', '', str_replace('/', '\\', substr($matches[0], 0, -4)));
            }
            if(strstr($file, 'kbit-admin')){
                $classname = '\\kbitAdmin\\' . \str_replace('src\\', '', str_replace('/', '\\', substr($matches[0], 1, -4)));
            }
            if(!strstr($classname, "\controller\\")){
                continue;
            }
            if (class_exists($classname)) {
                $c = new \ReflectionClass($classname);

                foreach ($c->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
                    list($function, $comment) = [$method->getName(), $method->getDocComment()];
                    if (stripos($function, '_') === 0 || in_array($function, self::$ignore)) continue;
                    $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                    // 插件中默认去掉  controller 控制器名
                    $controller = str_replace('controller.', '', $controller);
                    if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;


                    // 插件路由拼写
                    if(strstr($file, 'addons')){

                        $addons_sub_name = str_replace('addons-', '', $addons_name);

                        $node = "addons/" . self::parseString("{$addons_sub_name}/{$controller}") . '/' . strtolower($function);
                    }
                    // 通用管理后台入口拼写
                    if(strstr($file, 'kbit-admin')){
                        $node = "kbitadmin/" . self::parseString("{$controller}") . '/' . strtolower($function);
                    }
                    $nodes[$node] = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', preg_replace("/\s/", '', $comment));
                    if (stripos($nodes[$node], '@') !== false) $nodes[$node] = '';
                }
            }
        }
        return $nodes;
    }

    /**
     * 获取管理后台方法节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     * @throws \ReflectionException
     */
    public static function getKbitAdminMethodTreeNode($dir, $nodes = [], $folder = 'controller')
    {   
        foreach (self::scanDir($dir) as $file) {
            // die;
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/'.$folder.'/(.+)|', $filename, $matches)) continue;
            $classname = env('app_namespace') . str_replace('/', '\\', substr($matches[0], 0, -4));
            if(strstr($file, 'kbit-admin')){
                $classname = '\\kbitAdmin\\' . \str_replace('src\\', '', str_replace('/', '\\', substr($matches[0], 0, -4)));
            }
            if(strstr($classname, "\Base")){
                continue;
            }
            if(!strstr($classname, "\controller\\")){
                continue;
            }
            foreach ((new \ReflectionClass($classname))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
                list($function, $comment) = [$method->getName(), $method->getDocComment()];
                if (stripos($function, '_') === 0 || in_array($function, self::$ignore)) continue;
                $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                // 插件中默认去掉  controller 控制器名
                $controller = str_replace('controller.', '', $controller);
                if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;
                $node = "admin/" . self::parseString("{{$controller}") . '/' . strtolower($function);
                $nodes[$node] = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', preg_replace("/\s/", '', $comment));
                if (stripos($nodes[$node], '@') !== false) $nodes[$node] = '';
            }
        }
        return $nodes;
    }

    /**
     * 获取控制器节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     * @throws \ReflectionException
     */
    public static function getClassTreeNode($dir, $nodes = [], $folder = 'controller')
    {
        $file_list = self::scanDir($dir);
        foreach ($file_list as $file) {
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/'. $folder .'/(.+)|', $filename, $matches)) continue;
            $classname = env('app_namespace') . str_replace('/', '\\', substr($matches[0], 0, -4));
            if (class_exists($classname)) {
                $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;
                $node = self::parseString("{$matches[1]}/{$controller}");
                $comment = (new \ReflectionClass($classname))->getDocComment();
                $nodes[$node] = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', preg_replace("/\s/", '', $comment));
                if (stripos($nodes[$node], '@') !== false) $nodes[$node] = '';
            }
        }
        return $nodes;
    }

    /**
     * 获取插件节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     * @throws \ReflectionException
     */
    public static function getAddonsClassTreeNode($dir, $nodes = [], $folder = 'controller', $addons_name = '')
    {
        $file_list = self::scanDir($dir);
        foreach ($file_list as $file) {
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/'. $folder .'/(.+)|', $filename, $matches)) continue;
            if(strstr($file, 'addons')){
                $classname = '\\kbitAddons\\' . $addons_name . \str_replace('src\\', '', str_replace('/', '\\', substr($matches[0], 0, -4)));
            }
            if (class_exists($classname)) {
                $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;
                $node = self::parseString("{$addons_name}/{$controller}");
                $comment = (new \ReflectionClass($classname))->getDocComment();
                $nodes[$node] = preg_replace('/^\/\*\*\*(.*?)\*.*?$/', '$1', preg_replace("/\s/", '', $comment));
                if (stripos($nodes[$node], '@') !== false) $nodes[$node] = '';
            }
        }
        return $nodes;
    }

    /**
     * 获取节点列表
     * @param string $dir 控制器根路径
     * @param array $nodes 额外数据
     * @return array
     */
    public static function getTree($dir, $nodes = [])
    {
        foreach (self::scanDir($dir) as $file) {
            list($matches, $filename) = [[], str_replace(DIRECTORY_SEPARATOR, '/', $file)];
            if (!preg_match('|/(\w+)/controller/(.+)|', $filename, $matches)) continue;
            $classname = env('app_namespace') . str_replace('/', '\\', substr($matches[0], 0, -4));
            if (class_exists($classname)) foreach (get_class_methods($classname) as $function) {
                if (stripos($function, '_') === 0 || in_array($function, self::$ignore)) continue;
                $controller = str_replace('/', '.', substr($matches[2], 0, -4));
                if (stripos($controller, 'api.') !== false || stripos($controller, 'wap.') !== false) continue;
                $nodes[] = self::parseString("{$matches[1]}/{$controller}") . '/' . strtolower($function);
            }
        }
        return $nodes;
    }

    /**
     * 驼峰转下划线规则
     * @param string $node 节点名称
     * @return string
     */
    public static function parseString($node)
    {
        $nodes = [];
        foreach (explode('/', $node) as $str) {
            $dots = [];
            foreach (explode('.', $str) as $dot) array_push($dots, strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $dot), "_")));
            $nodes[] = join('.', $dots);
        }
        return trim(join('/', $nodes), '/');
    }

    /**
     * 获取所有PHP文件
     * @param string $dir 目录
     * @param array $data 额外数据
     * @param string $ext 有文件后缀
     * @return array
     */
    public static function scanDir($dir, $data = [], $ext = 'php')
    {
        if(is_dir($dir)){
            foreach (scandir($dir) as $curr) if (strpos($curr, '.') !== 0) {
                $path = realpath($dir . DIRECTORY_SEPARATOR . $curr);
                if (is_dir($path)) $data = array_merge($data, self::scanDir($path));
                elseif (pathinfo($path, PATHINFO_EXTENSION) === $ext) $data[] = $path;
            }
        }
        return $data;
    }

    /**
     * 递归统计目录大小
     * @param string $path 目录
     * @return integer
     */
    public static function totalDirSize($path)
    {
        list($total, $path) = [0, realpath($path)];
        if (!file_exists($path)) return $total;
        if (!is_dir($path)) return filesize($path);
        if ($handle = opendir($path)) {
            while ($file = readdir($handle)) if (!in_array($file, ['.', '..'])) {
                $temp = $path . DIRECTORY_SEPARATOR . $file;
                $total += (is_dir($temp) ? self::totalDirSize($temp) : filesize($temp));
            }
            if (is_resource($handle)) closedir($handle);
        }
        return $total;
    }

}